//
// Created by Administrator on 2020/9/9.
//

#include <iostream>
#include <cstdlib>
#include <deque>
#include <list>
#include <memory>
#include <set>
#include <unordered_map>
#include "nlohmann/json.hpp"
#include "asio.hpp"
#include "tcp_message.h"

using asio::ip::tcp;
using json = nlohmann::json;
using chat_message_queue = std::deque<chat_message>;
//----------------------------------------------------------------------

class chat_participant
{
public:
    virtual ~chat_participant() {}
    virtual void deliver(const chat_message& msg) = 0;
    void set_name(std::string who) { name_ = who; };
    std::string get_name() const { return name_; };
    
private:
    std::string name_;
};

using chat_participant_ptr = std::shared_ptr<chat_participant>;
using chat_message_map = std::unordered_map<chat_participant_ptr, chat_message>;
//----------------------------------------------------------------------

class chat_room
{
public:
    static constexpr int MaxRecentMsgs = 200;
    void join(chat_participant_ptr participant)
    {
        participants_.insert(participant);
        std::cout << "Enter: " << participant->get_name() << std::endl;
        std::cout << "peer num add : " << participants_.size() << std::endl;
        participants_.insert(participant);

        for (auto iter = recent_msgs_.begin(); iter!= recent_msgs_.end(); iter++)
        {
            participant->deliver(iter->second);
        }
    }

    void leave(chat_participant_ptr participant)
    {
        std::cout << "Leave: " << participant->get_name() << std::endl;
        participants_.erase(participant);
        std::cout << "peer num plus : " << participants_.size() << std::endl;
        recent_msgs_.erase(participant);
    }

    void deliver(chat_participant_ptr participant, const chat_message& msg)
    {
        recent_msgs_[participant] = msg;


        for (auto participant: participants_)
        {
            participant->deliver(msg);
        }
    }

private:
    std::set<chat_participant_ptr> participants_;
    chat_message_map recent_msgs_;
};

//----------------------------------------------------------------------

class chat_session
        : public chat_participant,
          public std::enable_shared_from_this<chat_session>
{
public:
    chat_session(tcp::socket socket, chat_room& room)
            : socket_(std::move(socket)),
              room_(room),
              result_num_(0)
    {
        set_name(socket_.remote_endpoint().address().to_string());
    }

    void start()
    {
        room_.join(shared_from_this());

        do_read_header();
    }

    void deliver(const chat_message& msg)
    {
        bool write_in_progress = !write_msgs_.empty();
        write_msgs_.push_back(msg);
        if (!write_in_progress)
        {
            do_write();
        }
    }

private:
    void do_read_header()
    {
        auto self(shared_from_this());
        asio::async_read(socket_,
                         asio::buffer(read_msg_.data(), chat_message::header_length),
                         [this, self](std::error_code ec, std::size_t /*length*/)
                         {
                             if (!ec && read_msg_.decode_header())
                             {
                                 do_read_body();
                             }
                             else
                             {
                                 room_.leave(shared_from_this());
                             }
                         });
    }

    void do_read_body()
    {
        auto self(shared_from_this());
        asio::async_read(socket_,
                         asio::buffer(read_msg_.body(), read_msg_.body_length()),
                         [this, self](std::error_code ec, std::size_t length)
                         {
                             if (!ec)
                             {
                                 try{
                                     std::string message(read_msg_.body(), length);
                                     std::cout << "message: "<< message << std::endl;
                                     if(message.find("result") != std::string::npos)
                                     {
                                         result_num_++;
                                         std::cout << "result_num_: " << result_num_ << std::endl;
                                     }
                                     else if(message.find("peer") != std::string::npos)
                                     {
                                         room_.deliver(shared_from_this(),read_msg_);
                                     }
                                     else
                                     {
                                         std::cout << "error message" << std::endl;
                                     }
                                 }
                                 catch(std::system_error)
                                 {
                                     std::cout << "message: " << std::string(read_msg_.body(), read_msg_.body_length()) << std::endl;
                                 }

                                 do_read_header();
                             }
                             else
                             {
                                 room_.leave(shared_from_this());
                             }
                         });
    }

    void do_write()
    {
        auto self(shared_from_this());
        asio::async_write(socket_,
                          asio::buffer(write_msgs_.front().data(),
                                       write_msgs_.front().length()),
                          [this, self](std::error_code ec, std::size_t /*length*/)
                          {
                              if (!ec)
                              {
                                  write_msgs_.pop_front();
                                  if (!write_msgs_.empty())
                                  {
                                      do_write();
                                  }
                              }
                              else
                              {
                                  room_.leave(shared_from_this());
                              }
                          });
    }

    tcp::socket socket_;
    chat_room& room_;
    chat_message read_msg_;
    chat_message_queue write_msgs_;
    std::size_t result_num_;
};

//----------------------------------------------------------------------

class chat_server
{
public:
    chat_server(asio::io_context& io_context,
                const tcp::endpoint& endpoint)
            : acceptor_(io_context, endpoint)
    {
        do_accept();
    }

private:
    void do_accept()
    {
        acceptor_.async_accept(
                [this](std::error_code ec, tcp::socket socket)
                {
                    if (!ec)
                    {
                        std::make_shared<chat_session>(std::move(socket), room_)->start();
                    }

                    do_accept();
                });
    }

    tcp::acceptor acceptor_;
    chat_room room_;
};

int main(int argc, char* argv[]) {

    if (argc < 2)
    {
        std::cerr << "Usage: chat_server <port> [<port> ...]\n";
        return 1;
    }
    asio::io_context io_context;

    std::list<chat_server> servers;
    for (int i = 1; i < argc; ++i)
    {
        tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));
        servers.emplace_back(io_context, endpoint);
    }

    io_context.run();

    return 0;
}

